package pl.com.qapps.datastore.berkeley;

import java.io.File;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import pl.com.qapps.datastore.Datastore;
import pl.com.qapps.datastore.GaeHelper;
import pl.com.qapps.datastore.Store;
import pl.com.qapps.datastore.base.AsyncDatastoreServiceBase;
import pl.com.qapps.datastore.base.DatastoreBase;
import pl.com.qapps.datastore.base.DatastoreServiceImpl;
import pl.com.qapps.datastore.berkeley.impl.BerkeleyStore;

import com.google.appengine.api.datastore.AsyncDatastoreService;
import com.google.appengine.api.datastore.DatastoreFailureException;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceConfig;
import com.sleepycat.je.EnvironmentLockedException;

/**
 * DatastoreService implementation build on the top of BerkeleyDB. Make only one
 * instance of specified datastore. When the home folder changes you can make
 * more instances. For now the implementation differs a bit from the original
 * one. For instance there is no quotas, less restrictions, the performance is
 * Different, all indexes are written before put method returns. Also, for now,
 * the transaction takes all entity groups, not only one or five, like the
 * original. But the differences aren't that much significant, so the code
 * running on Google Datastore should work pretty the same with this
 * implementation.
 * 
 * @author Jakub Dykowski
 * 
 */
public final class BerkeleyDatastore implements Datastore {

	private static final ExecutorService GLOBAL_DISPATCHER = Executors
			.newCachedThreadPool(new GaeSafeFactory());

	private final Store store;
	private final ExecutorService dispatcher;

	/**
	 * Not for public usage.
	 */
	private BerkeleyDatastore(BerkeleyStore store, ExecutorService dispatcher) {
		this.store = Objects.requireNonNull(store, "store cannot be null");
		this.dispatcher = Objects.requireNonNull(dispatcher,
				"dispatcher cannot be null");
	}

	@Override
	public DatastoreService getDatastoreService(DatastoreServiceConfig cfg) {
		if (cfg == null)
			cfg = makeDefaults();
		return new DatastoreServiceImpl(cfg, store, new TransactionStackImpl(),
				dispatcher);
	}

	@Override
	public AsyncDatastoreService getAsyncDatastoreService(
			DatastoreServiceConfig cfg) {
		if (cfg == null)
			cfg = makeDefaults();
		return new AsyncDatastoreServiceBase(cfg, store,
				new TransactionStackImpl(), dispatcher);
	}

	@Override
	public void close() {
		try {
			store.close();
		} catch (IOException e) {
			throw new DatastoreFailureException(
					"cannot close properly the datastore", e);
		}
	}

	/**
	 * A blocking call that serves access to the datastore through network. When
	 * the datastore is closed all blocked calls returns.
	 * 
	 * @param port
	 */
	public void serve(int port) {
		throw new UnsupportedOperationException("not supported yet");
	}

	/**
	 * Datastore (directory) is used exclusively, so multiple attempt to create
	 * instances of the same path will fail.
	 * 
	 * @param path
	 *            the directory of the datastore, must exist
	 * @return running and ready for use datastore
	 * @throws IllegalArgumentException
	 *             if specified path doesn't exist, is't directory, is already
	 * @throws NullPointerException
	 *             if path is null
	 */
	public static Datastore newLocal(String path)
			throws IllegalArgumentException, NullPointerException {
		Objects.requireNonNull(path, "path cannot be null");
		File home = new File(path);
		if (!home.exists())
			throw new IllegalArgumentException("directory '"
					+ home.getAbsolutePath()
					+ "' doesn't exists, create it first");
		if (!home.isDirectory())
			throw new IllegalArgumentException(home.getAbsolutePath()
					+ " is not a directory");
		try {
			Store store = new BerkeleyStore(home);
			return new DatastoreBase(store, GLOBAL_DISPATCHER) {

			};
		} catch (EnvironmentLockedException e) {
			throw new IllegalArgumentException(home.getAbsolutePath()
					+ " is already running", e);
		}
	}

	/**
	 * May be from the same host
	 * @param url
	 * @param port
	 * @return
	 */
	public static Datastore newRemote(String url, int port) {
		// TODO implement in future
		throw new UnsupportedOperationException(" not supported yet");
	}

	private static DatastoreServiceConfig makeDefaults() {
		return DatastoreServiceConfig.Builder.withDefaults();
	}

	private static class GaeSafeFactory implements ThreadFactory {

		@Override
		public Thread newThread(final Runnable runn) {
			return new Thread(new Runnable() {

				@Override
				public void run() {
					GaeHelper.registerEnvironment();
					runn.run();
				}

			});
		}

	}

}
